﻿using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace wRPCService
{
    /// <summary>
    /// 缓存管理器
    /// </summary>
    public class CacheManager
    {
        /// <summary>
        /// 默认缓存滑动窗口
        /// </summary>
        public static readonly TimeSpan defaultSlidingExpiration = TimeSpan.FromSeconds(60);
        /// <summary>
        /// 默认缓存固定窗口
        /// </summary>
        public static readonly TimeSpan defaultAbsoluteExpiration = TimeSpan.FromMinutes(60);
        /// <summary>
        /// 默认内存缓存对象
        /// </summary>
        internal static MemoryCache _cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
        /// <summary>
        /// 依赖关系内存缓存对象
        /// </summary>
        private static MemoryCache _dependCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
        /// <summary>
        /// 缓存过期后通知Token取消，以便使关联的缓存也过期
        /// </summary>
        private static PostEvictionDelegate EvictionCallback = (object key, object value, EvictionReason reason, object state)
            =>
        {
            _dependCache.GetOrCreate(key, o => new CancellationTokenSource()).Cancel();
            _dependCache.Remove(key);
        };
        /// <summary>
        /// 创建缓存过期策略
        /// </summary>
        /// <param name="absoluteExpiration">固定窗口</param>
        /// <param name="slidingExpiration">滑动窗口</param>
        /// <param name="dependCacheList">依赖缓存列表</param>
        /// <returns>缓存过期策略</returns>
        public static MemoryCacheEntryOptions CreateCachePolicy(TimeSpan absoluteExpiration, TimeSpan slidingExpiration, params object[] dependCacheList)
        {
            var policy = new MemoryCacheEntryOptions();
            if (absoluteExpiration > TimeSpan.Zero) policy.SetAbsoluteExpiration(absoluteExpiration);
            if (slidingExpiration > TimeSpan.Zero) policy.SetSlidingExpiration(slidingExpiration);
            policy.RegisterPostEvictionCallback(EvictionCallback);
            //依赖缓存键
            if (dependCacheList != null && dependCacheList.Count() > 0)
            {
                foreach (var depend in dependCacheList)
                {
                    var cts = _dependCache.GetOrCreate(depend, o => new CancellationTokenSource());
                    policy.AddExpirationToken(new CancellationChangeToken(cts.Token));
                }
            }
            return policy;
        }
        /// <summary>
        /// 创建缓存过期策略
        /// </summary>
        /// <param name="absoluteExpiration">固定窗口</param>
        /// <param name="slidingExpiration">滑动窗口</param>
        /// <returns>缓存过期策略</returns>
        public static MemoryCacheEntryOptions CreateCachePolicy(TimeSpan absoluteExpiration, TimeSpan slidingExpiration)
            => CreateCachePolicy(absoluteExpiration, slidingExpiration, null);
        /// <summary>
        /// 创建缓存过期策略(默认窗口时间)
        /// </summary>
        /// <param name="dependCacheList">依赖缓存列表</param>
        /// <returns>缓存过期策略</returns>
        public static MemoryCacheEntryOptions CreateCachePolicy(params string[] dependCacheList)
            => CreateCachePolicy(TimeSpan.Zero, TimeSpan.Zero, dependCacheList);

        /// <summary>
        /// 原生缓存方法调用
        /// </summary>
        /// <typeparam name="TItem">缓存数据类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="factory">缓存值获取方法，需要手动实现条目依赖等信息</param>
        /// <param name="policy">缓存过期策略，可以通过CacheManager.CreateCachePolicy获取基本策略，如果为null则设置为默认策略(固定窗口60分，滑动窗口1分)，自定义实现需自行实现缓存依赖。</param>
        /// <returns>缓存值</returns>
        public static async Task<TItem> GetOrCreateAsync<TItem>(object key, Func<ICacheEntry, Task<TItem>> factory, MemoryCacheEntryOptions policy = null)
        {
            if (policy == null) policy = CreateCachePolicy(defaultAbsoluteExpiration, defaultSlidingExpiration);
            return await _cache.GetOrCreateAsync(key,async entry => { entry.SetOptions(policy); return await factory(entry); });
        }
        /// <summary>
        /// 原生缓存方法调用
        /// </summary>
        /// <typeparam name="TItem">缓存数据类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="factory">缓存值获取方法，需要手动实现条目依赖等信息</param>
        /// <param name="policy">缓存过期策略，可以通过CacheManager.CreateCachePolicy获取基本策略，如果为null则设置为默认策略(固定窗口60分，滑动窗口1分)，自定义实现需自行实现缓存依赖。</param>
        /// <returns>缓存值</returns>
        public static TItem GetOrCreate<TItem>(object key, Func<ICacheEntry, TItem> factory, MemoryCacheEntryOptions policy = null)
        {
            if (policy == null) policy = CreateCachePolicy(defaultAbsoluteExpiration, defaultSlidingExpiration);
            return _cache.GetOrCreate(key, entry => { entry.SetOptions(policy); return factory(entry); });
        }

        /// <summary>
        /// 获取或创建缓存
        /// </summary>
        /// <typeparam name="TItem">要返回值的类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="factory">未缓存时获取值的方法</param>
        /// <param name="policy">缓存过期策略，可以通过CacheManager.CreateCachePolicy获取基本策略，如果为null则设置为默认策略(固定窗口60分，滑动窗口1分)，自定义实现需自行实现缓存依赖。</param>
        /// <returns>缓存后的对象</returns>
        public static TItem GetOrCreate<TItem>(object key, Func<TItem> factory, MemoryCacheEntryOptions policy = null)
        {
            if (policy == null) policy = CreateCachePolicy(defaultAbsoluteExpiration, defaultSlidingExpiration);
            return _cache.GetOrCreate(key, entry => { entry.SetOptions(policy); return factory(); });
        }

        /// <summary>
        /// 获取或创建缓存
        /// </summary>
        /// <typeparam name="TItem">要返回值的类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="factory">未缓存时获取值的方法</param>
        /// <param name="policy">缓存过期策略，可以通过CacheManager.CreateCachePolicy获取基本策略，如果为null则设置为默认策略(固定窗口60分，滑动窗口1分)，自定义实现需自行实现缓存依赖。</param>
        /// <returns>缓存后的对象</returns>
        public static async Task<TItem> GetOrCreateAsync<TItem>(object key, Func<Task<TItem>> factory, MemoryCacheEntryOptions policy = null)
        {
            if (policy == null) policy = CreateCachePolicy(defaultAbsoluteExpiration, defaultSlidingExpiration);
            return await _cache.GetOrCreateAsync(key, async entry => { entry.SetOptions(policy); return await factory(); });
        }

        /// <summary>
        /// 获取或创建缓存
        /// </summary>
        /// <typeparam name="TItem"></typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="factory">未缓存时获取值的方法</param>
        /// <param name="policy">缓存过期策略，可以通过CacheManager.CreateCachePolicy获取基本策略，如果为null则设置为默认策略(固定窗口60分，滑动窗口1分)，自定义实现需自行实现缓存依赖。</param>
        /// <returns>缓存后的对象</returns>
        public static TItem GetOrCreate<TItem>(object key, Func<MemoryCacheEntryOptions, TItem> factory, MemoryCacheEntryOptions policy = null)
        {
            if (policy == null) policy = CreateCachePolicy(defaultAbsoluteExpiration, defaultSlidingExpiration);
            return _cache.GetOrCreate(key, entry => { entry.SetOptions(policy); return factory(policy); });
        }

        /// <summary>
        /// 获取或创建缓存
        /// </summary>
        /// <typeparam name="TItem"></typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="factory">未缓存时获取值的方法</param>
        /// <param name="policy">缓存过期策略，可以通过CacheManager.CreateCachePolicy获取基本策略，如果为null则设置为默认策略(固定窗口60分，滑动窗口1分)，自定义实现需自行实现缓存依赖。</param>
        /// <returns>缓存后的对象</returns>
        public static async Task<TItem> GetOrCreateAsync<TItem>(object key, Func<MemoryCacheEntryOptions, Task<TItem>> factory, MemoryCacheEntryOptions policy = null)
        {
            if (policy == null) policy = CreateCachePolicy(defaultAbsoluteExpiration, defaultSlidingExpiration);
            return await _cache.GetOrCreateAsync(key, async entry => { entry.SetOptions(policy); return await factory(policy); });
        }

        /// <summary>
        /// 删除缓存
        /// </summary>
        /// <param name="key">缓存键</param>
        public static void Remove(object key) {
            _cache.Remove(key);
            _dependCache.GetOrCreate(key, o => new CancellationTokenSource()).Cancel();
            _dependCache.Remove(key);
        }
    }
}